// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/app_list/app_list_item_list.h"

#include "ui/app_list/app_list_item.h"

namespace app_list {

AppListItemList::AppListItemList()
{
}

AppListItemList::~AppListItemList()
{
}

void AppListItemList::AddObserver(AppListItemListObserver* observer)
{
    observers_.AddObserver(observer);
}

void AppListItemList::RemoveObserver(AppListItemListObserver* observer)
{
    DCHECK(observers_.HasObserver(observer));
    observers_.RemoveObserver(observer);
}

AppListItem* AppListItemList::FindItem(const std::string& id)
{
    for (size_t i = 0; i < app_list_items_.size(); ++i) {
        AppListItem* item = app_list_items_[i];
        if (item->id() == id)
            return item;
    }
    return NULL;
}

bool AppListItemList::FindItemIndex(const std::string& id, size_t* index)
{
    for (size_t i = 0; i < app_list_items_.size(); ++i) {
        AppListItem* item = app_list_items_[i];
        if (item->id() == id) {
            *index = i;
            return true;
        }
    }
    return false;
}

void AppListItemList::MoveItem(size_t from_index, size_t to_index)
{
    DCHECK_LT(from_index, item_count());
    DCHECK_LT(to_index, item_count());
    if (from_index == to_index)
        return;

    AppListItem* target_item = app_list_items_[from_index];
    DVLOG(2) << "MoveItem: " << from_index << " -> " << to_index << " ["
             << target_item->position().ToDebugString() << "]";

    // Remove the target item
    app_list_items_.weak_erase(app_list_items_.begin() + from_index);

    // Update the position
    AppListItem* prev = to_index > 0 ? app_list_items_[to_index - 1] : NULL;
    AppListItem* next = to_index < item_count() ? app_list_items_[to_index] : NULL;
    CHECK_NE(prev, next);
    syncer::StringOrdinal new_position;
    if (!prev) {
        new_position = next->position().CreateBefore();
    } else if (!next) {
        new_position = prev->position().CreateAfter();
    } else {
        // It is possible that items were added with the same ordinal. To
        // successfully move the item we need to fix this. We do not try to fix this
        // when an item is added in order to avoid possible edge cases with sync.
        if (prev->position().Equals(next->position()))
            FixItemPosition(to_index);
        new_position = prev->position().CreateBetween(next->position());
    }
    target_item->set_position(new_position);

    DVLOG(2) << "Move: "
             << " Prev: " << (prev ? prev->position().ToDebugString() : "(none)")
             << " Next: " << (next ? next->position().ToDebugString() : "(none)")
             << " -> " << new_position.ToDebugString();

    // Insert the item and notify observers.
    app_list_items_.insert(app_list_items_.begin() + to_index, target_item);
    FOR_EACH_OBSERVER(AppListItemListObserver,
        observers_,
        OnListItemMoved(from_index, to_index, target_item));
}

void AppListItemList::SetItemPosition(AppListItem* item,
    syncer::StringOrdinal new_position)
{
    DCHECK(item);
    size_t from_index;
    if (!FindItemIndex(item->id(), &from_index)) {
        LOG(ERROR) << "SetItemPosition: Not in list: " << item->id().substr(0, 8);
        return;
    }
    DCHECK(app_list_items_[from_index] == item);
    if (!new_position.IsValid()) {
        size_t last_index = app_list_items_.size() - 1;
        if (from_index == last_index)
            return; // Already last item, do nothing.
        new_position = app_list_items_[last_index]->position().CreateAfter();
    }
    // First check if the order would remain the same, in which case just update
    // the position.
    size_t to_index = GetItemSortOrderIndex(new_position, item->id());
    if (to_index == from_index) {
        DVLOG(2) << "SetItemPosition: No change: " << item->id().substr(0, 8);
        item->set_position(new_position);
        return;
    }
    // Remove the item and get the updated to index.
    app_list_items_.weak_erase(app_list_items_.begin() + from_index);
    to_index = GetItemSortOrderIndex(new_position, item->id());
    DVLOG(2) << "SetItemPosition: " << item->id().substr(0, 8) << " -> "
             << new_position.ToDebugString() << " From: " << from_index
             << " To: " << to_index;
    item->set_position(new_position);
    app_list_items_.insert(app_list_items_.begin() + to_index, item);
    FOR_EACH_OBSERVER(AppListItemListObserver,
        observers_,
        OnListItemMoved(from_index, to_index, item));
}

void AppListItemList::HighlightItemInstalledFromUI(const std::string& id)
{
    // Items within folders are not highlighted (apps are never installed to a
    // folder initially). So just search the top-level list.
    size_t index;
    if (FindItemIndex(highlighted_id_, &index)) {
        item_at(index)->set_highlighted(false);
        FOR_EACH_OBSERVER(AppListItemListObserver,
            observers_,
            OnAppListItemHighlight(index, false));
    }
    highlighted_id_ = id;
    if (!FindItemIndex(highlighted_id_, &index)) {
        // If the item isin't in the app list yet, it will be highlighted later, in
        // AddItem().
        return;
    }

    item_at(index)->set_highlighted(true);
    FOR_EACH_OBSERVER(
        AppListItemListObserver, observers_, OnAppListItemHighlight(index, true));
}

// AppListItemList private

syncer::StringOrdinal AppListItemList::CreatePositionBefore(
    const syncer::StringOrdinal& position)
{
    if (app_list_items_.empty())
        return syncer::StringOrdinal::CreateInitialOrdinal();

    size_t nitems = app_list_items_.size();
    size_t index;
    if (!position.IsValid()) {
        index = nitems;
    } else {
        for (index = 0; index < nitems; ++index) {
            if (!app_list_items_[index]->position().LessThan(position))
                break;
        }
    }
    if (index == 0)
        return app_list_items_[0]->position().CreateBefore();
    if (index == nitems)
        return app_list_items_[nitems - 1]->position().CreateAfter();
    return app_list_items_[index - 1]->position().CreateBetween(
        app_list_items_[index]->position());
}

AppListItem* AppListItemList::AddItem(scoped_ptr<AppListItem> item_ptr)
{
    AppListItem* item = item_ptr.get();
    CHECK(std::find(app_list_items_.begin(), app_list_items_.end(), item)
        == app_list_items_.end());
    EnsureValidItemPosition(item);
    size_t index = GetItemSortOrderIndex(item->position(), item->id());
    app_list_items_.insert(app_list_items_.begin() + index, item_ptr.release());
    FOR_EACH_OBSERVER(AppListItemListObserver,
        observers_,
        OnListItemAdded(index, item));

    if (item->id() == highlighted_id_) {
        // Item not present when highlight requested, so highlight it now.
        item->set_highlighted(true);
        FOR_EACH_OBSERVER(AppListItemListObserver,
            observers_,
            OnAppListItemHighlight(index, true));
    }
    return item;
}

void AppListItemList::DeleteItem(const std::string& id)
{
    scoped_ptr<AppListItem> item = RemoveItem(id);
    // |item| will be deleted on destruction.
}

scoped_ptr<AppListItem> AppListItemList::RemoveItem(const std::string& id)
{
    size_t index;
    if (!FindItemIndex(id, &index))
        LOG(FATAL) << "RemoveItem: Not found: " << id;
    return RemoveItemAt(index);
}

scoped_ptr<AppListItem> AppListItemList::RemoveItemAt(size_t index)
{
    CHECK_LT(index, item_count());
    AppListItem* item = app_list_items_[index];
    app_list_items_.weak_erase(app_list_items_.begin() + index);
    FOR_EACH_OBSERVER(AppListItemListObserver,
        observers_,
        OnListItemRemoved(index, item));
    return make_scoped_ptr<AppListItem>(item);
}

void AppListItemList::DeleteItemAt(size_t index)
{
    scoped_ptr<AppListItem> item = RemoveItemAt(index);
    // |item| will be deleted on destruction.
}

void AppListItemList::EnsureValidItemPosition(AppListItem* item)
{
    syncer::StringOrdinal position = item->position();
    if (position.IsValid())
        return;
    size_t nitems = app_list_items_.size();
    if (nitems == 0) {
        position = syncer::StringOrdinal::CreateInitialOrdinal();
    } else {
        position = app_list_items_[nitems - 1]->position().CreateAfter();
    }
    item->set_position(position);
}

size_t AppListItemList::GetItemSortOrderIndex(
    const syncer::StringOrdinal& position,
    const std::string& id)
{
    DCHECK(position.IsValid());
    for (size_t index = 0; index < app_list_items_.size(); ++index) {
        if (position.LessThan(app_list_items_[index]->position()) || (position.Equals(app_list_items_[index]->position()) && (id < app_list_items_[index]->id()))) {
            return index;
        }
    }
    return app_list_items_.size();
}

void AppListItemList::FixItemPosition(size_t index)
{
    DVLOG(1) << "FixItemPosition: " << index;
    size_t nitems = item_count();
    DCHECK_LT(index, nitems);
    DCHECK_GT(index, 0u);
    // Update the position of |index| and any necessary subsequent items.
    // First, find the next item that has a different position.
    AppListItem* prev = app_list_items_[index - 1];
    size_t last_index = index + 1;
    for (; last_index < nitems; ++last_index) {
        if (!app_list_items_[last_index]->position().Equals(prev->position()))
            break;
    }
    AppListItem* last = last_index < nitems ? app_list_items_[last_index] : NULL;
    for (size_t i = index; i < last_index; ++i) {
        AppListItem* cur = app_list_items_[i];
        if (last)
            cur->set_position(prev->position().CreateBetween(last->position()));
        else
            cur->set_position(prev->position().CreateAfter());
        prev = cur;
    }
    FOR_EACH_OBSERVER(AppListItemListObserver,
        observers_,
        OnListItemMoved(index, index, app_list_items_[index]));
}

} // namespace app_list
